#include "fiber.h"
#include "config.h"
#include "macro.h"
#include "log.h"
#include "scheduler.h"

#include <atomic>


namespace sylar
{

static Logger::LoggerPtr g_logger = SYLAR_LOG_NAME("system");
static std::atomic<uint64_t> s_fiber_id = {0};          // 总的协程id
static std::atomic<uint64_t> s_fiber_count = {0};       // 

// 个人理解： 
// t_fiber： 线程中的主协程，线程无法得到子协程需要主协程协助
// t_threadFiber： 主协程中使用 管理子协程 

static thread_local Fiber* t_RunFiber = nullptr;           // 线程当前正在执行的协程
static thread_local Fiber::ptr t_MainthreadFiber = nullptr; // 当前线程的主协程

static ConfigVar<uint32_t>::ptr g_fiber_stack_size = 
    Config::LookUp<uint32_t>("fiber.stack_size", 1024*1024, "fiber stack size");



// 栈内存分配器
class MallocStackAllocator
{
public:
    static void* Alloc(size_t size)
    {
        return malloc(size);
    }

    static void DeAlloc(void* p, size_t size)
    {
        return free(p);
    }

};

using StackAllocator = MallocStackAllocator;

// 当前正在执行的 协程 设置
void Fiber::SetThis(Fiber *f)
{
    t_RunFiber = f;
}


// 获取当前正在执行的协程 没有协程会创建一个主协程否则返回一个当前线程运行协程
Fiber::ptr Fiber::GetThis()
{
    if(t_RunFiber)
    {
        return t_RunFiber->shared_from_this();
    }

    Fiber::ptr main_fiber(new Fiber);
    SYLAR_ASSERT(t_RunFiber == main_fiber.get());
    t_MainthreadFiber = main_fiber;
    return t_RunFiber->shared_from_this();
}

// 协程挂入后台并设置ready状态
void Fiber::YaildToReady()
{
    Fiber::ptr cur = Fiber::GetThis();
    SYLAR_ASSERT(cur->m_state == EXEC);
    cur->m_state = READY;
    cur->swapOut();
}

// 协程挂入后台并设置hold状态
void Fiber::YaildToHold()
{
    Fiber::ptr curFiber = Fiber::GetThis();
    SYLAR_ASSERT(curFiber->m_state == EXEC);
    //cur->m_state = HOLD;
    curFiber->swapOut();
}

uint64_t Fiber::TotalFibers()
{
    return s_fiber_count;
}

void Fiber::MainFunc()
{
    Fiber::ptr cur = Fiber::GetThis(); // GetThis()的shared_from_this()方法让引用计数加1
    SYLAR_ASSERT(cur);

    try
    {
        cur->m_cb();
        //SYLAR_LOG_DEBUG(g_logger) << cur->getId();
        cur->m_cb = nullptr;        // function 可能会用到一些shard_ptr防止内存泄漏
        cur->m_state = TERM;
    }
    catch(std::exception& ex)
    {
        cur->m_state = EXCEPT;
        SYLAR_LOG_ERROR(g_logger) << "Fiber Exception: " << ex.what()
        << "fiber_id " << cur->getId()
        << std::endl
        << sylar::BacktraceToStrig();
    }
    catch(...)
    {
        cur->m_state = EXCEPT;
        SYLAR_LOG_ERROR(g_logger) << "Fiber Exception: ";
    }
    
    auto raw_ptr = cur.get();
    cur.reset();   // swapOut()后 cur 一直在协程栈上，引用计数没有减一 这里手动使引用计数减一(这里使用weak_ptr是不是更好)
    raw_ptr->swapOut();  //切换这里防止栈提前返回
    // raw_ptr->back();  // test_fiber打开否则会因为没有调度器指针崩溃
}

void Fiber::CallerMainFunc()
{
    auto cur = Fiber::GetThis();
    SYLAR_ASSERT(cur);

    try
    {
        cur->m_cb();
        
        cur->m_cb = nullptr;        // function 可能会用到一些shard_ptr防止内存泄漏
        cur->m_state = TERM;
    }
    catch(const std::exception& e)
    {
        cur->m_state = EXCEPT;
        SYLAR_LOG_ERROR(g_logger) << "Fiber Exception: " << e.what()
        << "fiber_id " << cur->getId()
        << std::endl
        << sylar::BacktraceToStrig();
    }
    catch(...)
    {
        cur->m_state = EXCEPT;
        SYLAR_LOG_ERROR(g_logger) << "Fiber Exception: ";
    }

    auto raw_ptr = cur.get();
    cur.reset();   // swapOut()后 cur 一直在协程栈上，引用计数没有减一 这里手动使引用计数减一(这里使用weak_ptr是不是更好)
   // raw_ptr->back();  //切换这里防止栈提前返回
   raw_ptr->swapOut();
}

uint64_t Fiber::GetFiberId()
{
    if (t_RunFiber)
    {
       return  t_RunFiber->getId();
    }
    return 0;
}

// 将当前线程的上下文赋值给 m_ctx (主协程使用)
Fiber::Fiber()
{
    // 主协程
    m_state = EXEC;
    SetThis(this);

    //获取当前上下文: 一些栈的状态，
    if(getcontext(&m_ctx))
    {
        SYLAR_ASSERT2(false, "getcontext") ;
    }
     GetFiberId();
    ++s_fiber_count;
    SYLAR_LOG_DEBUG(g_logger) << "Fiber::Fiber main: state: " << EXEC;
}

// 真正的协程(子协程)
Fiber::Fiber(std::function<void()> cb, int stacksize, bool user_caller)
    : m_id (++s_fiber_id)
    , m_cb(cb)
{
    ++s_fiber_count;
    m_stacksize = stacksize ? stacksize : g_fiber_stack_size->getValue();

    // 栈的生成
    m_stack = StackAllocator::Alloc(m_stacksize);
    if (getcontext(&m_ctx))
    {
        SYLAR_ASSERT2(false, "getcontext") ;
    }

    m_ctx.uc_link = nullptr;    // 上下文关联的生成,协程结束后自动回到指向的上下文
    m_ctx.uc_stack.ss_sp = m_stack;     // 指向栈空间
    m_ctx.uc_stack.ss_size = m_stacksize;   // 栈空空间大小

    if (!user_caller)
    {
        makecontext(&m_ctx, &Fiber::MainFunc, 0);   // 将函数与栈空间上下文关联
    }
    else
    {
        makecontext(&m_ctx, &Fiber::CallerMainFunc, 0);
    }
    SYLAR_LOG_DEBUG(g_logger) << "Fiber::Fiber id=" << m_id;
}

Fiber::~Fiber()
{
    --s_fiber_count;
    if (m_stack)
    {   // 子协程
        SYLAR_LOG_ERROR(g_logger) << "chiled state " << m_state;
        SYLAR_ASSERT(TERM == m_state || INIT == m_state || EXCEPT == m_state);
        StackAllocator::DeAlloc(m_stack, m_stacksize);
    }
    else
    {   // 主协程没有栈
        SYLAR_ASSERT(!m_cb);    // m_cb:没有
        SYLAR_ASSERT(EXEC == m_state);
        SYLAR_LOG_ERROR(g_logger) << "main  state" << m_state;
        Fiber* pFiber = t_RunFiber;
        if (pFiber == this)
        {
            SetThis(nullptr);
        }
    }
    SYLAR_LOG_DEBUG(g_logger) << "Fiber::~Fiber id=" << m_id
                              << " total=" << s_fiber_count;
}

// 充分利用内存
void Fiber::reset(std::function<void()> cb)
{
    SYLAR_ASSERT(m_stack);
    SYLAR_ASSERT(TERM == m_state || INIT == m_state || EXCEPT == m_state);   // 其他状态说明正在执行协程

    m_cb = cb;

    if (getcontext(&m_ctx))
    {
        SYLAR_ASSERT2(false, "getcontext error");
    }

    m_ctx.uc_link = nullptr;
    m_ctx.uc_stack.ss_sp = m_stack;
    m_ctx.uc_stack.ss_size = m_stacksize;

    makecontext(&m_ctx, &Fiber::MainFunc, 0);
    m_state = INIT;
}  


// 切换协程，将正在运行的协程切换到后台，将当前协程运行(线程的主协程切换到当前协程) 
void Fiber::swapIn()
{
    SetThis(this);      // 设置当前运行的协程指针 使用GetThis()可以获取
    SYLAR_ASSERT(EXEC != m_state);
    m_state = EXEC;
    // 上下文切换
    if (swapcontext(&Scheduler::GetMainFiber()->m_ctx, &m_ctx)) 
    {
        SYLAR_ASSERT2(false, "swapcontex error");
    }

}

// 把当前协程挂起（从当前的协程切换到线程的主协程）
void Fiber::swapOut()
{
    SetThis(Scheduler::GetMainFiber());
    if (swapcontext(&m_ctx, &Scheduler::GetMainFiber()->m_ctx))
    {
        SYLAR_ASSERT2(false, "swapcontex error");
    }
}

void Fiber::call()
{
    SetThis(this);
    m_state = EXEC;
    if (swapcontext(&t_MainthreadFiber->m_ctx, &m_ctx))
    {
        SYLAR_ASSERT2(false, "swapcontex error");
    }
}
void Fiber::back()
{
    SetThis(t_MainthreadFiber.get());
    if(swapcontext(&m_ctx, &t_MainthreadFiber->m_ctx)) 
    {
        SYLAR_ASSERT2(false, "swapcontext");
    }
}
}